package com.ssy.lingxi.scheduler.serviceimpl;

import com.ssy.lingxi.common.exception.BusinessException;
import com.ssy.lingxi.common.response.ResponseCode;
import com.ssy.lingxi.common.response.Wrapper;
import com.ssy.lingxi.scheduler.api.model.*;
import com.ssy.lingxi.scheduler.entity.ScheduleTaskDefinition;
import com.ssy.lingxi.scheduler.listener.PermanentJob;
import com.ssy.lingxi.scheduler.listener.ScheduleJob;
import com.ssy.lingxi.scheduler.model.ScheduleTaskTypeEnum;
import com.ssy.lingxi.scheduler.model.ServiceConstants;
import com.ssy.lingxi.scheduler.repository.ScheduleTaskDefinitionRepository;
import com.ssy.lingxi.scheduler.service.IScheduleTaskService;
import com.ssy.lingxi.scheduler.utils.ScheduleTaskUtil;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Quartz任务相关接口实现类
 * @author 万宁
 * @version 2.0.0
 * @date 2021-07-12
 */
@Service
public class ScheduleTaskServiceImpl implements IScheduleTaskService {
    private final static Logger log = LoggerFactory.getLogger(ScheduleTaskServiceImpl.class);

    @Resource
    private Scheduler scheduler;

    @Resource
    private ScheduleTaskDefinitionRepository taskDefinitionRepository;

    /**
     * 创建定时任务
     *
     * @param taskVO 接口参数
     * @return 定时任务的Id
     */
    @Transactional(rollbackFor = BusinessException.class)
    @Override
    public Wrapper<Long> createScheduleTask(ScheduleTaskVO taskVO) {
        //保存任务
        ScheduleTaskDefinition taskDefinition = new ScheduleTaskDefinition();
        taskDefinition.setCreateTime(LocalDateTime.now());
        taskDefinition.setServiceName(taskVO.getServiceName());
        taskDefinition.setCallbackUrl(taskVO.getCallbackUrl());
        taskDefinition.setMemberId(taskVO.getMemberId());
        taskDefinition.setRoleId(taskVO.getRoleId());
        taskDefinition.setDataIds(taskVO.getDataIds());
        taskDefinition.setExecTime(taskVO.getExecTime());
        taskDefinition.setExecDateTime(ScheduleTaskUtil.parseTimestamp(taskVO.getExecTime()));
        taskDefinition.setIntervalMinutes(ServiceConstants.TASK_INTERVAL_MINUTES);
        taskDefinition.setTaskType(ScheduleTaskTypeEnum.NORMAL.getCode());
        String taskName = ScheduleTaskUtil.generateRandomIdentity();
        taskDefinition.setTaskName(taskName);
        taskDefinition.setTaskGroup(taskVO.getServiceName());
        taskDefinition.setTaskAttributes(CollectionUtils.isEmpty(taskVO.getTaskAttributes()) ? new HashMap<>() : taskVO.getTaskAttributes());
        taskDefinition.setExecCount(0);
        taskDefinitionRepository.saveAndFlush(taskDefinition);

        // 将任务Id作为参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ServiceConstants.TASK_ID, taskDefinition.getId());

        JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class).setJobData(dataMap).withIdentity(taskName, taskVO.getServiceName()).build();

        //任务10分钟重复执行一次，重复执行100次
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(taskName, taskVO.getServiceName())
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMinutes(ServiceConstants.TASK_INTERVAL_MINUTES)
                        .withRepeatCount(ServiceConstants.TASK_REPEAT_TIMES))
                .startAt(new Date(taskVO.getExecTime()))
                .build();

        try {
            // 将任务及其触发器放入调度器，不需要 scheduler.start()
            scheduler.scheduleJob(jobDetail, trigger);

            return Wrapper.success(taskDefinition.getId());
        } catch (Exception e) {
            log.error("定时任务创建失败:" + e.getMessage());
            //这里要抛出异常，不能return Wrapper.fail()，否则事务不会回滚
            throw new BusinessException(ResponseCode.SS_TASK_CREATE_FAILED, e.getMessage());
        }
    }

    /**
     * 创建永久任务
     *
     * @param taskVO 接口参数
     * @return 定时任务的Id
     */
    @Override
    public Wrapper<Long> createPermanentTask(PermanentTaskVO taskVO) {
        ScheduleTaskDefinition taskDefinition = taskDefinitionRepository.findFirstByTaskName(taskVO.getTaskName());
        if(taskDefinition != null) {
            Boolean isCreateNew = taskVO.getIsCreateNew();
            if(isCreateNew){
                //删除旧任务
                if(!deleteExistPermanentTask(taskDefinition.getTaskName(), taskDefinition.getTaskGroup())) {
                    return Wrapper.fail(ResponseCode.SS_TASK_DELETE_FAILED);
                }
            }else{
                return Wrapper.success(taskDefinition.getId());
            }
        } else {
            taskDefinition = new ScheduleTaskDefinition();
        }

        //保存任务
        taskDefinition.setCreateTime(LocalDateTime.now());
        taskDefinition.setServiceName(taskVO.getServiceName());
        taskDefinition.setCallbackUrl(taskVO.getCallbackUrl());
        taskDefinition.setMemberId(ScheduleTaskUtil.isNullOrNegativeZero(taskVO.getMemberId()) ? 0L : taskVO.getMemberId());
        taskDefinition.setRoleId(ScheduleTaskUtil.isNullOrNegativeZero(taskVO.getRoleId()) ? 0L : taskVO.getRoleId());
        taskDefinition.setDataIds(CollectionUtils.isEmpty(taskVO.getDataIds()) ? new ArrayList<>() : taskVO.getDataIds());
        taskDefinition.setExecTime(ScheduleTaskUtil.getPermanentTaskStartTime(taskVO.getStartMinutes()));
        taskDefinition.setExecDateTime(ScheduleTaskUtil.parseTimestamp(taskDefinition.getExecTime()));
        taskDefinition.setIntervalMinutes(ScheduleTaskUtil.isNullOrNegativeZero(taskVO.getIntervalMinutes()) ? ServiceConstants.DAY_INTERVAL_MINUTES : taskVO.getIntervalMinutes());
        taskDefinition.setTaskType(ScheduleTaskTypeEnum.PERMENANT.getCode());
        taskDefinition.setTaskName(taskVO.getTaskName());
        taskDefinition.setTaskGroup(taskVO.getServiceName());
        taskDefinition.setTaskAttributes(CollectionUtils.isEmpty(taskVO.getTaskAttributes()) ? new HashMap<>() : taskVO.getTaskAttributes());
        taskDefinition.setExecCount(0);
        taskDefinitionRepository.saveAndFlush(taskDefinition);

        // 将任务Id作为参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ServiceConstants.TASK_ID, taskDefinition.getId());

        JobDetail jobDetail = JobBuilder.newJob(PermanentJob.class).setJobData(dataMap).withIdentity(taskVO.getTaskName(), taskVO.getServiceName()).build();

        //任务永久执行
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(taskVO.getTaskName(), taskVO.getServiceName())
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMinutes(taskDefinition.getIntervalMinutes())
                        .repeatForever())
                .startAt(new Date(taskDefinition.getExecTime()))
                .build();

        try {
            // 将任务及其触发器放入调度器，不需要 scheduler.start()
            scheduler.scheduleJob(jobDetail, trigger);
            return Wrapper.success(taskDefinition.getId());
        } catch (Exception e) {
            log.error("永久定时任务创建失败:" + e.getMessage());
            //这里要抛出异常，不能return Wrapper.fail()，否则事务不会回滚
            throw new BusinessException(ResponseCode.SS_TASK_CREATE_FAILED, e.getMessage());
        }
    }

    /**
     * 查询定时任务信息，如任务已经执行完成，返回空
     *
     * @param queryVO 接口参数
     * @return 查询结果
     */
    @Override
    public Wrapper<List<ScheduleTaskDefinitionVO>> listScheduleTask(ScheduleTaskQueryVO queryVO) {
        Specification<ScheduleTaskDefinition> specification = (Specification<ScheduleTaskDefinition>) (root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();

            if(queryVO.getTaskId() != null && queryVO.getTaskId() > 0) {
                list.add(criteriaBuilder.equal(root.get("id").as(Long.class), queryVO.getTaskId()));
            }

            if(StringUtils.hasLength(queryVO.getServiceName())) {
                list.add(criteriaBuilder.equal(root.get("serviceName").as(String.class), queryVO.getServiceName()));
            }

            if(queryVO.getMemberId() != null && queryVO.getMemberId() > 0) {
                list.add(criteriaBuilder.equal(root.get("memberId").as(Long.class), queryVO.getMemberId()));
            }

            if(queryVO.getRoleId() != null && queryVO.getRoleId() > 0) {
                list.add(criteriaBuilder.equal(root.get("roleId").as(Long.class), queryVO.getRoleId()));
            }

            Predicate[] p = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(p));
        };

        List<ScheduleTaskDefinition> taskDefinitions = taskDefinitionRepository.findAll(specification, Sort.by("id").ascending());

        return Wrapper.success(taskDefinitions.stream().map(taskDefinition -> {
            ScheduleTaskDefinitionVO definitionVO = new ScheduleTaskDefinitionVO();
            definitionVO.setTaskId(taskDefinition.getId());
            definitionVO.setCreateTime(taskDefinition.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            definitionVO.setServiceName(taskDefinition.getServiceName());
            definitionVO.setCallbackUrl(taskDefinition.getCallbackUrl());
            definitionVO.setMemberId(taskDefinition.getMemberId());
            definitionVO.setRoleId(taskDefinition.getRoleId());
            definitionVO.setDataIds(taskDefinition.getDataIds());
            definitionVO.setExecTime(taskDefinition.getExecTime());
            definitionVO.setExecTimeStr(taskDefinition.getExecDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            definitionVO.setTaskName(taskDefinition.getTaskName());
            definitionVO.setTaskGroup(taskDefinition.getTaskGroup());
            definitionVO.setTaskAttributes(taskDefinition.getTaskAttributes());
            definitionVO.setExecCount(taskDefinition.getExecCount());
            return definitionVO;
        }).collect(Collectors.toList()));
    }

    /**
     * 修改任务的启动时间
     *
     * @param updateVO 接口参数
     * @return 修改结果
     */
    @Override
    public Wrapper<Void> updateScheduleTaskExecTime(ScheduleTaskUpdateExecTimeVO updateVO) {
        ScheduleTaskDefinition taskDefinition = taskDefinitionRepository.findById(updateVO.getTaskId()).orElse(null);
        if(taskDefinition == null) {
            return Wrapper.fail(ResponseCode.SS_TASK_DOES_NOT_EXIST);
        }

        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(taskDefinition.getTaskName(), taskDefinition.getTaskGroup())
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInMinutes(ServiceConstants.TASK_INTERVAL_MINUTES)
                        .withRepeatCount(ServiceConstants.TASK_REPEAT_TIMES))
                .startAt(new Date(updateVO.getExecTime()))
                .build();
        TriggerKey triggerKey = TriggerKey.triggerKey(taskDefinition.getTaskName(), taskDefinition.getTaskGroup());

        try {
            //重新调度任务
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (Exception e) {
            return Wrapper.fail(ResponseCode.SERVICE_ERROR, e.getMessage());
        }

        taskDefinition.setExecTime(updateVO.getExecTime());
        taskDefinitionRepository.saveAndFlush(taskDefinition);
        return Wrapper.success();
    }

    /**
     * 调用方执行完成定时任务后，通知定时任务模块
     *
     * @param idVO 接口参数
     * @return 通知结果
     */
    @Override
    public Wrapper<Void> deleteScheduleTask(ScheduleTaskIdVO idVO) {
        ScheduleTaskDefinition taskDefinition = taskDefinitionRepository.findById(idVO.getTaskId()).orElse(null);
        if(taskDefinition == null) {
            return Wrapper.success();
        }

        deleteScheduleTask(taskDefinition.getId(), taskDefinition.getTaskName(), taskDefinition.getTaskGroup());
        return Wrapper.success();
    }

    /**
     * 删除已经存在的永久任务
     *
     * @param taskName  任务名称
     * @param taskGroup 任务组
     * @return 删除成功-true，失败-false
     */
    @Override
    public boolean deleteExistPermanentTask(String taskName, String taskGroup) {
        try {
            TriggerKey key = new TriggerKey(taskName, taskGroup);
            //停止触发器
            scheduler.pauseTrigger(key);
            // 移除触发器
            scheduler.unscheduleJob(key);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(taskName, taskGroup));
            return true;
        } catch (Exception e) {
            log.error("定时任务删除失败:" + e.getMessage() + ", taskName:" + taskName + ", taskGroup:" + taskGroup);
            return false;
        }
    }

    /**
     * 删除任务定义，删除Quartz任务
     * @param taskId 任务Id
     * @param taskName 任务名称
     * @param taskGroup 任务组
     */
    @Override
    public void deleteScheduleTask(Long taskId, String taskName, String taskGroup) {
        try {
            TriggerKey key = new TriggerKey(taskName, taskGroup);
            //停止触发器
            scheduler.pauseTrigger(key);
            // 移除触发器
            scheduler.unscheduleJob(key);
            // 删除任务
            scheduler.deleteJob(JobKey.jobKey(taskName, taskGroup));

            taskDefinitionRepository.deleteById(taskId);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("定时任务删除错误" + e.getMessage());
        }
    }

    /**
     * 删除Quartz任务
     *
     * @param jobDetail Quartz任务
     */
    @Override
    public void deleteQuartzJob(Trigger trigger, JobDetail jobDetail) {
        if(trigger == null || jobDetail == null) {
            return;
        }

        try {
            //停止触发器
            scheduler.pauseTrigger(trigger.getKey());
            // 移除触发器
            scheduler.unscheduleJob(trigger.getKey());
            // 删除任务
            scheduler.deleteJob(jobDetail.getKey());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("删除Quartz任务错误：" + e.getMessage());
        }
    }
}
